﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace SD007
{
    public delegate void ProccessLineRules(List<VariableAbstraction> list, string line, string op, int opStartIndex, int opEndIndex);
    public static class GetVariablesUsedFromIL
    {
        private static string[] fieldOps = { "ldfld", "ldflda", "ldsfld", "ldsflda", "stfld", "stsfld" };
        private static string[] propOps = { "callvirt", "call" };
        public static List<VariableAbstraction> GetUsedInstanceVars(string IL)
        {
            return ProccessIL(IL, (list, line, op, opStartIndex, opEndIndex) =>
            {
                if (fieldOps.Contains(op))
                {
                    VariableAbstraction variable = GetVariable(line, line.IndexOf(" ", opEndIndex) + 1);
                    if (variable != null)
                        AddVariable(variable, list);
                }
            });
        }

        private static List<VariableAbstraction> ProccessIL(string IL, ProccessLineRules proccessLineRule)
        {
            List<VariableAbstraction> list = new List<VariableAbstraction>();
            using (StringReader reader = new StringReader(IL))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                    UsedVarsProcessLine(line, list, proccessLineRule);
            }
            return list;
        }

        private static void UsedVarsProcessLine(string line, List<VariableAbstraction> list, ProccessLineRules proccessLineRule)
        {
            //If is method call or accessor call
            line = line.Trim();
            int addressSeparatorIndex = line.IndexOf(" : ");
            string instructionAddress = line.Substring(0, addressSeparatorIndex + 1);
            int opIndex = addressSeparatorIndex + 3;
            int opEndIndex = (line.IndexOf(" ", opIndex) == -1) ? line.Length - 1 : (line.IndexOf(" ", opIndex) - 1);
            if (line.Length == opEndIndex + 1)
                return;
            string op = line.Substring(opIndex, opEndIndex - opIndex + 1);

            proccessLineRule.Invoke(list, line, op, opIndex, opEndIndex);

            //if (fieldOps.Contains(op))
            //{
            //    VariableAbstraction variable = GetVariable(line, line.IndexOf(" ", opEndIndex) + 1);
            //    if(variable != null)
            //        AddVariable(variable, list);
            //}
        }

        private static VariableAbstraction GetVariable(string line, int startFieldIndex)
        {
            if (line.Length > 0 && line.Last() == ')')
                return null;
            int teste = 0;
            int varTypeEndIndex = line.IndexOf(" ", startFieldIndex) - 1;
            string varTypeName = line.Substring(startFieldIndex, varTypeEndIndex - startFieldIndex + 1);
            int varNameSpaceStartIndex = line.IndexOf(" ", varTypeEndIndex) + 1;
            int varNameSpaceEndIndex = line.IndexOf("::", varNameSpaceStartIndex) - 1;
            string varNameSpace = line.Substring(varNameSpaceStartIndex, varNameSpaceEndIndex - varNameSpaceStartIndex + 1);
            int varNameStartIndex = varNameSpaceEndIndex + 3;
            int varNameEndIndex = line.Length - 1;
            string varName = line.Substring(varNameStartIndex, varNameEndIndex - varNameStartIndex + 1);
            return new VariableAbstraction(varName, varTypeName)
            {   
                OwnerType = varNameSpace
            };
            
        }

        private static void AddVariable(VariableAbstraction variable, List<VariableAbstraction> list)
        {
            List<VariableAbstraction> equalsVariables = list.Where(v => (
                (v.OwnerType == variable.OwnerType) && (v.Name == variable.Name) && (v.TypeName == variable.TypeName)
            )).ToList();
            if (equalsVariables.Count == 0)
            {
                list.Add(variable);
            }
        }

        private static void AddProperty(VariableAbstraction variable, List<VariableAbstraction> list)
        {
            List<VariableAbstraction> equalsVariables = list.Where(v => (
                (v.OwnerType == variable.OwnerType) 
                && (v.Name.Substring(4) == variable.Name.Substring(4))
            )).ToList();
            if (equalsVariables.Count == 0)
            {
                list.Add(variable);
            }
        }

        public static List<VariableAbstraction> GetAtfdVars(string IL, List<string> ownerClassesNames)
        {
            return ProccessIL(IL, (list, line, op, opStartIndex, opEndIndex) =>
            {
                if (fieldOps.Contains(op))
                {
                    VariableAbstraction variable = GetVariable(line, line.IndexOf(" ", opEndIndex) + 1);
                    bool containsVar = false;
                    foreach (var v in ownerClassesNames)
                        containsVar = containsVar || variable.OwnerType.EndsWith(v);
                    if (variable != null && !containsVar)
                        AddVariable(variable, list);
                }
            });
        }

        private static PropertyAbstraction GetProperty(string line, int startFieldIndex)
        {
            int propNameSpaceStartIindex = line.LastIndexOf(" ") + 1;
            int propNameSpaceEndIindex = line.IndexOf("::", propNameSpaceStartIindex) - 1;
            string propNameSpace = line.Substring(propNameSpaceStartIindex, propNameSpaceEndIindex - propNameSpaceStartIindex + 1);
            int propIsGetterStartIndex = propNameSpaceEndIindex + 3;
            string propIsGetter = line.Substring(propIsGetterStartIndex, 4);
            if (propIsGetter != "get_" && propIsGetter != "set_")
                return null;
            bool isGetter = propIsGetter == "get_";
            int propNameStartIndex = propIsGetterStartIndex;
            int propNameEndIndex = line.Length - 1;
            string propName = line.Substring(propNameStartIndex, propNameEndIndex - propNameStartIndex + 1);

            int propTypeStartIndex = line.Substring(0, propNameSpaceStartIindex).Trim().LastIndexOf(" ") + 1;
            int propTypeEndIndex = line.IndexOf(" ", propTypeStartIndex) - 1;
            string propType = line.Substring(propTypeStartIndex, propTypeEndIndex - propTypeStartIndex + 1);

            return new PropertyAbstraction(propName, propType)
            {
                OwnerType = propNameSpace,
                IsGetter = isGetter
            };

        }

        public static List<VariableAbstraction> GetAtfdProperties(string IL, List<string> ownerClassesNames)
        {
            List<VariableAbstraction> varList = ProccessIL(IL, (list, line, op, opStartIndex, opEndIndex) =>
            {
                if (propOps.Contains(op))
                {
                    PropertyAbstraction property = GetProperty(line, line.IndexOf(" ", opEndIndex) + 1);
                    bool containsVar = false;
                    foreach (var v in ownerClassesNames)
                        containsVar = containsVar || property.OwnerType.EndsWith(v);
                    if (property != null && !containsVar)
                        AddProperty(property, list);
                }
            });
            return varList;
        }

    }
}
